]> dgit.raspbian.org Git - gnome-shell.git/commitdiff
[AppWell] Allow popup menu to be persistent, and support direct window selection
authorColin Walters <walters@verbum.org>
Thu, 10 Sep 2009 01:14:31 +0000 (21:14 -0400)
committerColin Walters <walters@verbum.org>
Fri, 11 Sep 2009 22:57:44 +0000 (18:57 -0400)
When the user click+hold+release over the icon, the effect we want
is for the menu to stick around.

Also, allow the user to mouse over the actual windows and select
them directly.  If the user mouses over a window, reflect that in
the menu.

js/ui/appDisplay.js
js/ui/overview.js
js/ui/workspaces.js
src/shell-menu.c
src/shell-menu.h

index fe5b4329973cd291302d7060cf514484f577493d..92bfb70abc49c082aae4b80f1973d81e42f01d4e 100644 (file)
@@ -489,6 +489,12 @@ WellMenu.prototype = {
     _init: function(source) {
         this._source = source;
 
+        // Holds the WindowClone objects for our windows, used in the button release
+        // callback to find the window actor we released over
+        this._cachedWindowClones = [];
+
+        // Whether or not we successfully picked a window
+        this.didActivateWindow = false;
 
         this.actor = new Shell.GenericContainer({ reactive: true });
         this.actor.connect('get-preferred-width', Lang.bind(this, this._getPreferredWidth));
@@ -508,6 +514,14 @@ WellMenu.prototype = {
         this._windowContainer.connect('activate', Lang.bind(this, this._onWindowActivate));
         this.actor.add_actor(this._windowContainer);
 
+        // Stay popped up on release over application icon
+        this._windowContainer.set_persistent_source(this._source.actor);
+
+        // Intercept events while the menu has the pointer grab to do window-related effects
+        this._windowContainer.connect('enter-event', Lang.bind(this, this._onMenuEnter));
+        this._windowContainer.connect('leave-event', Lang.bind(this, this._onMenuLeave));
+        this._windowContainer.connect('button-release-event', Lang.bind(this, this._onMenuRelease));
+
         this._arrow = new Shell.DrawingArea();
         this._arrow.connect('redraw', Lang.bind(this, function (area, texture) {
             Shell.draw_box_pointer(texture, WELL_MENU_BORDER_COLOR, WELL_MENU_BACKGROUND_COLOR);
@@ -559,6 +573,8 @@ WellMenu.prototype = {
     _redisplay: function() {
         this._windowContainer.remove_all();
 
+        this.didActivateWindow = false;
+
         let windows = this._source.windows;
 
         this._windowContainer.show();
@@ -595,7 +611,10 @@ WellMenu.prototype = {
 
     _appendWindows: function (windows, iconsDiffer) {
         for (let i = 0; i < windows.length; i++) {
-            let window = windows[i];
+            let metaWindow = windows[i];
+
+            this._cachedWindowClones.push(Main.overview.lookupCloneForWindow(metaWindow));
+
             /* Use padding here rather than spacing in the box above so that
              * we have a larger reactive area.
              */
@@ -604,16 +623,16 @@ WellMenu.prototype = {
                                     padding_bottom: 4,
                                     spacing: 4,
                                     reactive: true });
-            box._window = window;
+            box._window = metaWindow;
             let vCenter;
             if (iconsDiffer) {
                 vCenter = new Big.Box({ y_align: Big.BoxAlignment.CENTER });
-                let icon = Shell.TextureCache.get_default().bind_pixbuf_property(window, "mini-icon");
+                let icon = Shell.TextureCache.get_default().bind_pixbuf_property(metaWindow, "mini-icon");
                 vCenter.append(icon, Big.BoxPackFlags.NONE);
                 box.append(vCenter, Big.BoxPackFlags.NONE);
             }
             vCenter = new Big.Box({ y_align: Big.BoxAlignment.CENTER });
-            let label = new Clutter.Text({ text: window.title,
+            let label = new Clutter.Text({ text: metaWindow.title,
                                            font_name: WELL_MENU_FONT,
                                            ellipsize: Pango.EllipsizeMode.END,
                                            color: WELL_MENU_COLOR });
@@ -639,22 +658,68 @@ WellMenu.prototype = {
         this.actor.show();
     },
 
-    _onWindowUnselected: function (actor, child) {
-        child.background_color = TRANSPARENT_COLOR;
+    _findWindowCloneForActor: function (actor) {
+        for (let i = 0; i < this._cachedWindowClones.length; i++) {
+            let clone = this._cachedWindowClones[i];
+            if (clone.actor == actor) {
+                return clone;
+            }
+        }
+        return null;
+    },
 
-        this.emit('highlight-window', null);
+    // This function is called while the menu has a pointer grab; what we want
+    // to do is see if the mouse was released over a window clone actor
+    _onMenuRelease: function (actor, event) {
+        let clone = this._findWindowCloneForActor(event.get_source());
+        if (clone) {
+            this.didActivateWindow = true;
+            Main.overview.activateWindow(clone.metaWindow, event.get_time());
+        }
     },
 
-    _onWindowSelected: function (actor, child) {
-        child.background_color = WELL_MENU_SELECTED_COLOR;
+    _setHighlightWindow: function (metaWindow) {
+        let children = this._windowContainer.get_children();
+        for (let i = 0; i < children.length; i++) {
+            let child = children[i];
+            let menuMetaWindow = child._window;
+            if (metaWindow != null && menuMetaWindow == metaWindow) {
+                child.background_color = WELL_MENU_SELECTED_COLOR;
+            } else {
+                child.background_color = TRANSPARENT_COLOR;
+            }
+        }
+        this.emit('highlight-window', metaWindow);
+    },
+
+    // Called while menu has a pointer grab
+    _onMenuEnter: function (actor, event) {
+        let clone = this._findWindowCloneForActor(event.get_source());
+        if (clone) {
+            this._setHighlightWindow(clone.metaWindow);
+        }
+    },
 
-        let window = child._window;
-        this.emit('highlight-window', window);
+    // Called while menu has a pointer grab
+    _onMenuLeave: function (actor, event) {
+        let clone = this._findWindowCloneForActor(event.get_source());
+        if (clone) {
+            this._setHighlightWindow(null);
+        }
+    },
+
+    _onWindowUnselected: function (actor, child) {
+        this._setHighlightWindow(null);
+    },
+
+    _onWindowSelected: function (actor, child) {
+        this._setHighlightWindow(child._window);
     },
 
     _onWindowActivate: function (actor, child) {
-        let window = child._window;
-        Main.overview.activateWindow(window, Clutter.get_current_event_time());
+        let metaWindow = child._window;
+        this.didActivateWindow = true;
+        Main.overview.activateWindow(metaWindow, Clutter.get_current_event_time());
     },
 
     _onPopdown: function () {
@@ -793,15 +858,22 @@ RunningWellItem.prototype = {
 
         if (this._menu == null) {
             this._menu = new WellMenu(this);
-            this._menu.connect('highlight-window', Lang.bind(this, function (menu, window) {
-                Main.overview.setHighlightWindow(window);
+            this._menu.connect('highlight-window', Lang.bind(this, function (menu, metaWindow) {
+                Main.overview.setHighlightWindow(metaWindow);
             }));
             this._menu.connect('popup', Lang.bind(this, function (menu, isPoppedUp) {
                 let id;
+
+                // If we successfully picked a window, don't reset the workspace
+                // state, since that causes visual noise.  The workspace gets
+                // recreated each time we enter the overview
+                if (!isPoppedUp && menu.didActivateWindow)
+                    return;
                 if (isPoppedUp)
                     id = this.appInfo.get_id();
                 else
                     id = null;
+
                 Main.overview.setApplicationWindowSelection(id);
             }));
         }
index 4f50e6fe812626e3b84ccc45cae7e2583c664c02..4a78db9ca05243eb23cd5775f73a31539b3964f1 100644 (file)
@@ -371,6 +371,17 @@ Overview.prototype = {
             this.show();
     },
 
+    /**
+     * lookupCloneForWindow:
+     * @metaWindow: A #MetaWindow
+     *
+     * Given a #MetaWindow instance, find the WindowClone object
+     * which represents it in the workspaces display.
+     */
+    lookupCloneForWindow: function (metaWindow) {
+        return this._workspaces.lookupCloneForMetaWindow(metaWindow);
+    },
+
     /**
      * activateWindow:
      * @metaWindow: A #MetaWindow
index 39c48b5aecc6ab0897281e71be5414529af222b3..be208f93b0b7791c3abb52d1ab35f19f72d6b71d 100644 (file)
@@ -1134,7 +1134,7 @@ Workspaces.prototype = {
         return null;
     },
 
-    _lookupCloneForMetaWindow: function (metaWindow) {
+    lookupCloneForMetaWindow: function (metaWindow) {
         for (let i = 0; i < this._workspaces.length; i++) {
             let clone = this._workspaces[i].lookupCloneForMetaWindow(metaWindow);
             if (clone)
@@ -1181,7 +1181,7 @@ Workspaces.prototype = {
         let activeWorkspaceNum = global.screen.get_active_workspace_index();
         let windowWorkspaceNum = metaWindow.get_workspace().index();
 
-        let clone = this._lookupCloneForMetaWindow (metaWindow);
+        let clone = this.lookupCloneForMetaWindow (metaWindow);
         clone.actor.raise_top();
 
         if (windowWorkspaceNum != activeWorkspaceNum) {
index 6debaf8f7d55ed21031aa0bec47e07d735359fa2..d09f6a0258b465d6e463506e57584582e9de95c1 100644 (file)
@@ -15,6 +15,9 @@ G_DEFINE_TYPE(ShellMenu, shell_menu, BIG_TYPE_BOX);
 struct _ShellMenuPrivate {
   gboolean have_grab;
 
+  gboolean released_on_source;
+  ClutterActor *source_actor;
+
   ClutterActor *selected;
 };
 
@@ -31,10 +34,10 @@ enum
 static guint shell_menu_signals [LAST_SIGNAL] = { 0 };
 
 static gboolean
-shell_menu_contains (ShellMenu     *box,
-                     ClutterActor  *actor)
+shell_menu_contains (ClutterContainer *container,
+                     ClutterActor     *actor)
 {
-  while (actor != NULL && actor != (ClutterActor*)box)
+  while (actor != NULL && actor != (ClutterActor*)container)
     {
       actor = clutter_actor_get_parent (actor);
     }
@@ -73,7 +76,7 @@ shell_menu_enter_event (ClutterActor         *actor,
 {
   ShellMenu *box = SHELL_MENU (actor);
 
-  if (!shell_menu_contains (box, event->source))
+  if (!shell_menu_contains (CLUTTER_CONTAINER (box), event->source))
     return TRUE;
 
   if (event->source == (ClutterActor*)box)
@@ -107,9 +110,21 @@ shell_menu_button_release_event (ClutterActor       *actor,
   if (event->button != 1)
     return FALSE;
 
+  if (box->priv->source_actor && !box->priv->released_on_source)
+    {
+      if (box->priv->source_actor == event->source ||
+          (CLUTTER_IS_CONTAINER (box->priv->source_actor) &&
+           shell_menu_contains (CLUTTER_CONTAINER (box->priv->source_actor), event->source)))
+        {
+          /* On the next release, we want to pop down the menu regardless */
+          box->priv->released_on_source = TRUE;
+          return TRUE;
+        }
+    }
+
   shell_menu_popdown (box);
 
-  if (!shell_menu_contains (box, event->source))
+  if (!shell_menu_contains (CLUTTER_CONTAINER (box), event->source))
     return FALSE;
 
   if (box->priv->selected == NULL)
@@ -126,6 +141,7 @@ shell_menu_popup (ShellMenu         *box,
                   guint32            activate_time)
 {
   box->priv->have_grab = TRUE;
+  box->priv->released_on_source = FALSE;
   clutter_grab_pointer (CLUTTER_ACTOR (box));
 }
 
@@ -138,6 +154,45 @@ shell_menu_popdown (ShellMenu *box)
   g_signal_emit (G_OBJECT (box), shell_menu_signals[POPDOWN], 0);
 }
 
+static void
+on_source_destroyed (ClutterActor *actor,
+                     ShellMenu    *box)
+{
+  box->priv->source_actor = NULL;
+}
+
+/**
+ * shell_menu_set_persistent_source:
+ * @box:
+ * @source: Actor to use as menu origin
+ *
+ * This function changes the menu behavior on button release.  Normally
+ * when the mouse is released anywhere, the menu "pops down"; when this
+ * function is called, if the mouse is released over the source actor,
+ * the menu stays.
+ *
+ * The given @source actor must be reactive for this function to work.
+ */
+void
+shell_menu_set_persistent_source (ShellMenu    *box,
+                                  ClutterActor *source)
+{
+  if (box->priv->source_actor)
+    {
+      g_signal_handlers_disconnect_by_func (G_OBJECT (box->priv->source_actor),
+                                            G_CALLBACK (on_source_destroyed),
+                                            box);
+    }
+  box->priv->source_actor = source;
+  if (box->priv->source_actor)
+    {
+      g_signal_connect (G_OBJECT (box->priv->source_actor),
+                        "destroy",
+                        G_CALLBACK (on_source_destroyed),
+                        box);
+    }
+}
+
 /**
  * shell_menu_append_separator:
  * @box:
index 1d0c57111cf656ea40c818867bac96697fffc0dd..bfe909adc46d8985b0431411757d1c80c9afe431 100644 (file)
@@ -32,6 +32,8 @@ GType shell_menu_get_type (void) G_GNUC_CONST;
 
 void shell_menu_popup (ShellMenu *behavior, guint button, guint32 activate_time);
 
+void shell_menu_set_persistent_source (ShellMenu *behavior, ClutterActor *source);
+
 void shell_menu_append_separator (ShellMenu *behavior, ClutterActor *separator, BigBoxPackFlags flags);
 
 void shell_menu_popdown (ShellMenu *behavior);